/*
 * @(#)WorkflowAutomaticTaskManager.java
 *
 * Copyright (c) 2006 DCIVision Ltd
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of DCIVision
 * Ltd ("Confidential Information").  You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the license
 * agreement you entered into with DCIVision Ltd.
 */
package com.dcivision.workflow.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.upload.FormFile;

import com.dcivision.framework.ApplicationException;
import com.dcivision.framework.SessionContainer;
import com.dcivision.framework.SystemParameterConstant;
import com.dcivision.framework.SystemParameterFactory;
import com.dcivision.framework.Utility;
/**
 * <p>Class Name:    WorkflowAutomaticTaskManager.java    </p>
  *
 * @author           tc //mod by greatwall shao
 * @company          DCIVision Limited
 * @creation date    2006-08-23
 */
public class WorkflowAutomaticTaskManager {

  protected Log log = LogFactory.getLog(this.getClass().getName());
  private SessionContainer sessionCon = null;
  private Connection dbConn = null;
  public WorkflowAutomaticTaskManager(SessionContainer sessionContainer, Connection conn) {
    this.sessionCon = sessionContainer;
    this.dbConn = conn;
  }

  public String saveAndCompile(FormFile srcFile) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, ApplicationException {
   if(srcFile.getFileName().trim().toLowerCase().endsWith(".class")){
     return saveClass(srcFile);
   }
    String classname = "";
    String fileDir = saveSrcFile(srcFile);
    compileFile(fileDir, true);
    classname = getTheWholeClassName(fileDir);
    if (isRightSubClass(classname))
      return classname;
    else {
      this.deleteSrcFile(classname);
      this.deleteBinFile(classname);
      throw new IOException("must implement AutomaticTaskInterface");
    }
  }
  
  private String saveClass(FormFile srcFile) throws ClassNotFoundException, FileNotFoundException, IOException, ApplicationException {
    String path = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_BINDIR);
    String tempFileName = String.valueOf(System.currentTimeMillis());
    String tempName = saveFile(srcFile,path,tempFileName+".class");    
    Class clazz= new DynaClassLoader().loadClass(tempFileName);
     if(!AutomaticTaskInterface.class.isAssignableFrom(clazz)&&!WorkflowProcessor.class.isAssignableFrom(clazz))
       throw new IOException("must implement AutomaticTaskInterface"); 
     String pakeageStr = "";
     String fileName = "";
     if(clazz.getName().indexOf(".")>-1)
     {
       pakeageStr = clazz.getName().substring(0,clazz.getName().lastIndexOf(".")).replace('.', '/');
      }
    path=path+"/"+pakeageStr;
    path=path.trim();
    fileName=clazz.getName().substring(clazz.getName().lastIndexOf(".")+1)+".class";
    File sFile = new File(tempName); 
    File dFile = new File(path,fileName);
  
    sFile.renameTo(dFile);
     return clazz.getName();
  }

  private boolean isRightSubClass(String classname) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
    Class clazz = AutomaticTaskFactory.getClassLoader().loadClass(classname);
    return AutomaticTaskInterface.class.isAssignableFrom(clazz)||WorkflowProcessor.class.isAssignableFrom(clazz);
  }

  private String saveSrcFile(FormFile srcFile) throws FileNotFoundException, IOException, ApplicationException {
    String filename ="";
    String srcDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_SRCDIR);
    filename = getJavaFileName(srcFile);
    String packageStr = getThePackageStr(srcFile.getInputStream());
    packageStr = null == packageStr ? "" : packageStr;
    packageStr = packageStr.replace('.', '/');
    srcDir = srcDir + "/" + packageStr;
    
    return saveFile(srcFile, srcDir, filename);

  }

  private String getJavaFileName(FormFile srcFile) throws IOException, FileNotFoundException {
    String filename="";
    String classStr = getTheClassStr(srcFile.getInputStream());
    if (null == classStr)
      throw new IOException("the class must implements AutomaticTaskInterface");    
     String[] names = classStr.split("\\s+");
    for(int i=0;i<names.length;i++)
    {
      if(names[i].trim().toLowerCase().equals("class"))
      {
        filename=names[++i]+".java";
        break;
      }
    }
    return filename;
  }

  private String saveFile(FormFile srcFile, String srcDir, String filename) throws IOException, ApplicationException {
    InputStream in = null;
    OutputStream out = null;
    File srcFileDir = findOrNewFileDir(srcDir);

    File tempFile = new File(srcFileDir, filename); 
    
    try {
      in = srcFile.getInputStream();
      out = new FileOutputStream(tempFile);
      byte[] bt = new byte[in.available()];
      int i = 0;
      while ((i = in.read(bt)) != -1) {
        out.write(bt, 0, i);
      }
      return tempFile.getAbsolutePath();
    } catch (IOException e) {
      
      log.error(e, e);
      throw new IOException(e.toString());
    } finally {
      try {
        out.flush();
        in.close();
        out.close();
      } catch (IOException e) {
        
        log.error(e, e);
      }
    }
  }

  public String getThePackageStr(File file) {
    String packageStr = null;
    BufferedReader reader = null;
    try {
      reader = new BufferedReader(new FileReader(file));
      try {
        packageStr = reader.readLine();
        while (Utility.isEmpty(packageStr)) {
          packageStr = reader.readLine();
        }
        reader.close();

        packageStr = packageStr.trim();
        packageStr = packageStr.substring(7).trim();

        return packageStr;
      } catch (IOException e) {
        
        log.error(e, e);
        return null;
      }
    } catch (FileNotFoundException e) {
      
      log.error(e, e);
    }
    return null;
  }

  public String getTheWholeClassName(FormFile srcFile) throws FileNotFoundException, IOException, ApplicationException, ClassNotFoundException {
    String filename = srcFile.getFileName();
    if(srcFile.getFileName().trim().toLowerCase().endsWith(".class")){
      //is class ,get the wholeClassName
      return getTheWholeClassNameByClassFile(srcFile);
    }else{
      //is java , get the wholeClassName
      return getTheWholeClassNameByJavaFile(srcFile);
    }
  }
  
  private String getTheWholeClassNameByClassFile(FormFile srcFile) throws ApplicationException, IOException, ClassNotFoundException{
    String path = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_BINDIR);
    String tempFileName = String.valueOf(System.currentTimeMillis());
    String tempName = saveFile(srcFile,path,tempFileName+".class");    
    Class clazz= new DynaClassLoader().loadClass(tempFileName);
     if(!AutomaticTaskInterface.class.isAssignableFrom(clazz)&&!WorkflowProcessor.class.isAssignableFrom(clazz))
       throw new IOException("must implement AutomaticTaskInterface"); 
    return clazz.getName();
  }
  private String getTheWholeClassNameByJavaFile(FormFile srcFile) throws FileNotFoundException, IOException{
    String filename = getJavaFileName(srcFile);
    filename = filename.substring(0, filename.lastIndexOf("."));
    String classname = "";
    String packageStr = getThePackageStr(srcFile.getInputStream());
    classname = (null == packageStr) ? filename:(packageStr + "." + filename);
    return classname; 
  }
  private String getTheWholeClassName(String getAbsolutePath) {
    String srcDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_SRCDIR);
    String wholePath = getAbsolutePath.substring(srcDir.length() + 1);
    int index = wholePath.lastIndexOf(".");
    if (index > 0) {
      wholePath = wholePath.substring(0, index);
    }
    wholePath = wholePath.replaceAll("\\\\|/", ".");
    return wholePath;
  }

  public boolean scanTheBinDirIfExistsTheClass(String wholeclassname) {
    String binDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_BINDIR);
    String classpath = wholeclassname.replace('.', '/');
    String filepath = binDir + "/" + classpath + ".class";
    File classFile = new File(filepath);
    if (classFile.exists())
      return true;
    else
      return false;
  }

  private String getThePackageStr(InputStream in) throws IOException {
    String packageStr = getThePatternStr(in, "\\s*package\\s+.+");
    if (null != packageStr) {
      packageStr = packageStr.substring("package".length()).trim();
      if(packageStr.indexOf(";")>-1)
      packageStr = packageStr.substring(0, packageStr.indexOf(";")).trim();
      return packageStr;
    }
    return null;
  }

  private String getTheClassStr(InputStream in) throws IOException {
    return getThePatternStr(in, "\\s*public\\s+(final\\s+|\\s*)class\\s+\\w+\\s+(extends|implements)\\s+\\w+.+");

  }

  private String getThePatternStr(InputStream in, String sPattern) {
    String patternStr = null;
    BufferedReader reader = null;
    reader = new BufferedReader(new InputStreamReader(in));
    try {
      patternStr = reader.readLine().trim();
      while (patternStr != null) {
        patternStr=patternStr.trim();
        if (patternStr.startsWith("/*")) {
          while (!patternStr.endsWith("*/") && patternStr != null) {
            patternStr = reader.readLine();
          }
          patternStr = reader.readLine();
          continue;
        }

        if (patternStr.startsWith("//")) {
          patternStr = reader.readLine();
          continue;
        }
        if (patternStr.matches(sPattern)) {
          return patternStr;
        }
        if (!patternStr.matches(sPattern)) {
          if (patternStr.matches("\\s*\\w*\\s*class\\s+\\w+.+")) {
            break;
          }
          patternStr = reader.readLine();
        }
        
      }
      return null;
    } catch (IOException e) {
      
      log.error(e, e);
      return null;
    } finally {
      try {
        reader.close();
        in.close();
      } catch (IOException e) {
        
        log.error(e, e);
      }
    }
  }

  private boolean deleteFile(File delFile) {
    String binDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_BINDIR);
    String srcDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_SRCDIR);

    try {
      binDir = new File(binDir).getCanonicalPath();
      srcDir = new File(srcDir).getCanonicalPath();
      boolean isDel = delFile.delete();
      if (isDel) {
        File parentFile = delFile.getParentFile();
        if (null != parentFile && parentFile.list().length == 0) {
          String path = parentFile.getCanonicalPath();
          if (!(path.trim().equalsIgnoreCase(binDir.trim()) || path.trim().equalsIgnoreCase(srcDir.trim())))
            return deleteFile(parentFile);
        }
      }
      return isDel;
    } catch (Exception ex) {
      log.error(ex, ex);
      return false;
    }
  }

  public boolean deleteSrcFile(String classname) {
    try {
      String srcDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_SRCDIR);
      return deleteSrcFile(srcDir, classname);
    } catch (Exception ex) {
      log.error(ex, ex);
      return false;
    }
  }

  public boolean deleteSrcFile(String srcDir, String filename) {
    try {
      File srcFile = new File(srcDir, filename);
      return deleteFile(srcFile);
    } catch (Exception ex) {
      log.error(ex, ex);
      return false;
    }
  }

  public boolean deleteBinFile(String binname) {
    try {
      String binDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_BINDIR);
      return deleteBinFile(binDir, binname);
    } catch (Exception ex) {
      log.error(ex, ex);
      return false;
    }
  }

  public boolean deleteBinFile(String BinDir, String binname) {
    try {
      File srcFile = new File(BinDir, binname);
      return deleteFile(srcFile);
    } catch (Exception ex) {
      log.error(ex, ex);
      return false;
    }
  }

  public String compileFile(String filename, boolean isPath) {
    String compileClasspath = extractClasspath(Thread.currentThread().getContextClassLoader());
    String outputDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_BINDIR);
    String srcFileDir = SystemParameterFactory.getSystemParameter(SystemParameterConstant.WORKFLOW_AUTOMATIC_TASK_SRCDIR);
    findOrNewFileDir(outputDir);

    File srcFile = null;
    if (isPath)
      srcFile = new File(filename);
    else
      srcFile = new File(srcFileDir, filename);

    Javac javac = new Javac(compileClasspath, outputDir);
    String result = javac.compile(new File[] { srcFile });
    if (result == null) {
      return null;
    } else {
      log.info("compile result:" + result);
      throw new AutoTaskCompileException("Failed to compile " + srcFile.getAbsolutePath() + ". Error: " + result);
    }
  }

  private File findOrNewFileDir(String fileDir) {
    File dir = new File(fileDir);
    if (!dir.exists())
      dir.mkdirs();
    return dir;
  }

  private static String extractClasspath(ClassLoader cl) {
    StringBuffer buf = new StringBuffer();

    while (cl != null) {
      if (cl instanceof URLClassLoader) {
        URL urls[] = ((URLClassLoader) cl).getURLs();
        for (int i = 0; i < urls.length; i++) {
          if (buf.length() > 0) {
            buf.append(File.pathSeparatorChar);
          }
          buf.append(urls[i].getFile().toString());
        }
      }
      cl = cl.getParent();
    }

    return buf.toString();
  }
}
